<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.js"></script> -->
</head>

<body>
    <div id="root" class="container">
        <div class="test test2">
            <p>{{name}}</p>
            <p>{{age}}</p>
        </div>
        <p>{{item.one}}</p>
    </div>
    <script>
        // 实现下面的功能
        // 找到dom，遍历子节点，正则替换文本标签中的内容
        /* 
        let vue = new Vue({
            el:'#root',
            data:{
                name:'飞飞',
                message:'你好啊！'
            }
        })
        */
        // 实现存在的问题
        /*
        vue中使用的是虚拟dom
        下面是实现了单属性{{name}},vue中可以是{{child.name}}
        */

        // 本次拓展功能，实现dom和虚拟dom的互相转换
        /* 
             <div></div> {tag:'div'}
             <div class='a' title='b' id='c'></div> {tag:'div',attr:{class:a,title:b,id:c},children:[{tag:'div'}……]}
        */
        //通过js的内置对象JSON来进行数组对象的深拷贝
        function deepClone2(obj) {
            var _obj = JSON.stringify(obj),
                objClone = JSON.parse(_obj);
            return objClone;
        }
        class Vue {
            constructor(options) {
                // console.log(options)
                // let {el,data} = options
                // 1.获取Dom元素-》2.获取data->3.生成一个虚拟DOM,将数据填入坑中，-》4.用虚拟dom替换原来的Dom
                this.el = options.el
                this.data = options.data
                this.root = document.querySelector(this.el)
                //  Cannot read property 'replaceChild' of null
                /* 这个bug之前是没有的，因为之前没有做数据的响应式和模板的重新渲染
                出现问题的原因就是，之前的虚拟dom替换真实dom之后，属性值没有保存下来，导致二次渲染的时候
                找不到父节点，所以这里先保存父节点 */
                this.parentNode = this.root.parentNode
                Reactive(this.data, this)
                InitProxy(this)
                this.exg = /\{\{(.*)\}\}/
                // this.copyRoot = this.root.cloneNode(true)
                // 下面使用真正的虚拟DOM
                this.virDOM = GetNode(document.querySelector(this.el))
                // console.log(this.virDOM)
                // this.templateCache = this.cache()
                this.update()
                // console.dir(this.root)
            }
            // 此函数只执行一次，只在初始化的时候执行一次进行模板的备份
           /*  cache() {
                //将虚拟dom复制一份
                let virDOM = GetNode(document.querySelector(this.el))
                console.log('缓存一份', (virDOM))
                return function (data) {
                    console.log(virDOM)
                    let vnode = null
                    let nodeName = virDOM.nodeName
                    let nodeType = virDOM.nodeType
                    let childNodes = JSON.parse(JSON.stringify(virDOM.childNodes))
                    let attributes = JSON.parse(JSON.stringify(virDOM.attributes))
                    vnode = new Node({ nodeName, nodeType, childNodes, attributes })
                    this.Complie(vnode, data)
                    return vnode
                }
            } */
            update() {
                // let res = this.templateCache(this.data)
                // console.log(res)
                // this.Render(res)
                // console.log(this.copyRoot)
                // console.log(this.virDOM)
                let cop = deepClone2(this.virDOM)
                this.Complie(cop, this.data)
                // console.log(this.root)
                this.Render(cop)
            }
            // 编译方法有问题，应该是数据和模板结合返回新的虚拟dom,之前这里直接
            //是在原来的模板上面进行的修改,所以修改入参为备份的dom
            Complie(node, data) {
                let children = node.childNodes //取出子元素
                for (let i = 0; i < children.length; i++) {
                    if (children[i].nodeType === 1) { //节点元素
                        this.Complie(children[i], data)
                        // console.log('递归')
                    } else if (children[i].nodeType === 3) { //文本节点
                        let val = children[i].nodeValue
                        // console.log(val)
                        let that = this
                        val = val.replace(this.exg, function (_, e) {
                            // return that.data[e.trim()]
                            // 这里解决只能使用单层的data信息问题:使用.去划分层级
                            return that.getDate(e)
                        })
                        children[i].nodeValue = val
                    }
                }
                // return vnode
            }
            Render(node) {
                // console.log(node)
                console.log(DOM(node))
                // 4将虚拟dom插入到真实DOM中 ,用他的父元素调用替换子元素的方法
                document.body.removeChild(document.getElementById('root'))
                document.body.appendChild(DOM(node))
                // console.log(DOM(node))
                // this.parentNode.replaceChild(DOM(node), this.root)
            }
            getDate(path) {
                // console.log(path)
                let obj = path.split(".")
                let res = this.data
                let len = obj.length
                for (let i = 0; i < len; i++) {
                    res = res[obj[i]]
                    // console.log(obj[i])
                }
                //console.log('res:',res)
                return res
            }
        }
        /* 虚拟dom和dom的转换模块 */
        class Node {
            constructor(node) {
                this.tag = node.nodeName
                this.nodeType = node.nodeType
                this.nodeValue = node.nodeValue || null
                this.childNodes = []
                this.attributes = (function (node) {
                    if (!node.attributes) return []
                    let temp = []
                    for (let i = 0; i < node.attributes.length; i++) {
                        temp.push({ nodeName: node.attributes[i].nodeName, nodeValue: node.attributes[i].nodeValue })
                        /*  console.log(node.attributes[i].nodeName)
                         console.dir(node) */
                    }
                    return temp
                })(node)
            }
            appendChild(node) {
                this.childNodes.push(node)
            }
        }
        // 将DOM转换成虚拟DOM
        function GetNode(node) {
            // console.dir(node)
            let list = node.childNodes
            // console.log(list)
            let vNode = new Node(node)
            if (node.nodeType === 1) {
                let list = node.childNodes
                for (let i = 0; i < list.length; i++) {
                    vNode.appendChild(GetNode(list[i]))
                }
            } else {

            }
            return vNode
        }
        // 将虚拟dom转换成真实DOM
        function DOM(virDOM) {
            let res = null
            if (virDOM.nodeType === 3) {
                res = document.createTextNode(virDOM.nodeValue)
            } else {
                // 创建节点
                res = document.createElement(virDOM.tag)
                // 将节点的属性添加上去
                // 目前就支持class和id
                for (let i in virDOM.attributes) {
                    let temp = virDOM.attributes[i]
                    // console.log(virDOM.attributes[i])
                    if (temp.nodeName === 'class') {
                        res.className = temp.nodeValue
                    } else {
                        res[temp.nodeName] = temp.nodeValue
                    }
                }
                for (let i = 0; i < virDOM.childNodes.length; i++) {
                    res.appendChild(DOM(virDOM.childNodes[i]))
                }
            }
            return res
        }
        /* let root = new Node(document.getElementById('root'))
        let test = GetNode(document.getElementById('root'))
        console.dir(test)
        let dom = DOM(test)
        console.log(dom) */
        /* let test = document.createTextNode('123')
        let test2 = document.createElement('DIV')
        // test.nodeValue = '1234'
        console.log(test2) */

        // ----------------------响应式实现模块-------------
        // 方法拦截
        let arr = []
        let method = ['push', 'pop', 'shift', 'unshift']
        let arr_method = Object.create(Array.prototype)
        method.forEach(method => {
            arr_method[method] = function () {
                console.log('调用的拦截方法')
                // 拦截的时候设置响应式，
                Reactive(arguments)
                let res = Array.prototype[method].apply(this, arguments)
                return res
            }
        })
        arr.__proto__ = arr_method
        /* 
        arr.__proto__ -> Array.prototype   .__prototype ->Object.prototype
        中间加一层
        arr.__proto__ -> temp    .__proto__->Array.prototype   .__prototype ->Object.prototype
        */

        /*  let obj = {
             name:'lihua',
             age:20,
             gender:'男',
             item:{
                 one:1,
                 two:2
             },
             goal:[
                 {math:99},
                 {english:100}
             ]
 
         } */
        function defineReactive(obj, key, val, vnode) {
            // console.log(obj,key,val),
            if (typeof (val) === 'object') {//这里包括数组和对象，数组也是特殊的对象
                Reactive(val)
                return
            }
            Object.defineProperty(obj, key, {
                get() {
                    console.log('访问属性' + key)
                    return val
                },
                set(nVal) {
                    console.log('设置属性' + key + ":" + nVal)
                    if (typeof (nVal) === 'object') {
                        Reactive(nVal)
                    }
                    val = nVal
                    //更新dom
                    vnode.update()
                }
            })
        }
        function Reactive(obj, vnode) {
            let keys = Object.keys(obj)
            // console.log(keys)
            for (let i = 0; i < keys.length; i++) {
                let key = keys[i]
                let val = obj[key]
                if (Array.isArray(val)) {//如果是数组,添加拦截方法
                    val.__proto__ = arr_method
                }
                // console.log(obj,key,val)
                // 如果是引用类型就需要进行递归
                // 如果不是引用类型需要调用defineReactive设置成响应式
                // 字面量或者是对象类型的数据
                defineReactive(obj, key, val, vnode)
            }
        }
        function InitProxy(vnode) {
            for(let key in vnode.data){
                // 实现属性的代理
                // 使用this.xxx 访问，代替this.data.xxx
                Proxy(vnode, 'data', key)
            }
        }
        function Proxy(obj, prop ,key) {
            console.log(obj)
            // 目前该方法仅支持简单的普通数据类型
            Object.defineProperty(obj,key,{
                get(){
                    return obj[prop][key]
                },
                set(newVal){
                    obj[prop][key] = newVal
                }
            })
        }
        let vue = new Vue({
            el: '#root',
            data: {
                name: 'lihua',
                age: 20,
                gender: '男',
                item: {
                    one: 1,
                    two: 2
                },
                goal: [
                    { math: 99 },
                    { english: 100 }
                ]

            }
        })

    </script>
</body>

</html>